/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package wf.units;
//import waterfall.domain.DObject.CannotComitException;
//import waterfall.domain.DPLC;

import bc.swing.pfrm.events.Event;
import bc.swing.pfrm.events.EventListener;
import bc.swing.pfrm.units.EventBusUnit;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import wf.model.Counter;
import wf.model.CounterData;
import wf.model.PLC;
import wf.model.Valve;
import wf.plccom.PLCHighLevel;
import wf.units.DatabaseUnit.ChangeType;

//import waterfall.domain.Domain;
/**
 *
 * @author student
 */
public enum PLCComUnit implements Runnable {

    UNIT;
    public static final int REQUEST_SEND_MAXIMUM_RETRIES = 3;
    public static final int MAINTANANCE_PERIOD_DELAY = 60 * 1000;
    private HashMap<PLC, PLCHighLevel> hlv;
    private LinkedBlockingQueue<Event> events = new LinkedBlockingQueue<Event>();
    
    @Override
    public void run() {
        List<PLC> plcs = DatabaseUnit.UNIT.selectAllPLCs();
        hlv = new HashMap<PLC, PLCHighLevel>();

        for (PLC plc : plcs) {
            hlv.put(plc, new PLCHighLevel(plc.getIp()));
        }
        Event e;
        
        registerToEvents();
        
        while (true) {
            try {
                System.err.println("DEAMON MAINTANANCE CYCLE START");
                
                while (!events.isEmpty()){
                    e = events.poll();
                    if (e != null){
                        handleEvent(e);
                    }
                }
                
                for (PLC plc : plcs) {
                    try {
                        maintain(plc);
                    } catch (Exception ex) {
                        Logger.getLogger(PLCComUnit.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }

                System.err.println("DEAMON MAINTANANCE CYCLE END");
                Thread.sleep(MAINTANANCE_PERIOD_DELAY);
            } catch (Exception ex) {
                Logger.getLogger(PLCComUnit.class.getName()).log(Level.SEVERE, null, ex);

            }
        }
    }

        
    private void registerToEvents(){
        EventBusUnit.UNIT.register(DatabaseUnit.DATABASE_CHANGED_EVENT, new EventListener() {

            @Override
            public void onEvent(Event e) {
                events.add(e);
            }
        });
    }
    
    public String sendRequest(String url, String ip) {
        Socket s = null;
        String request = "GET " + url + " HTTP/1.1\r\nUser-Agent:Waterfall\r\nHost: 192.168.0.30\r\nAccept: */*\r\n\r\n";
        System.err.println("Sending:\n-----------------\n" + request + "-----------------");

        for (int i = 0; i < REQUEST_SEND_MAXIMUM_RETRIES; i++) {
            try {
                s = new Socket(ip, 9080);
                s.getOutputStream().write(request.getBytes());
                BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));

                String line = "";
                while ((line = br.readLine()) != null) {
                    if (line.isEmpty()) {
                        break;
                    }
                }

                line = br.readLine();

                s.getInputStream().close();
                s.close();
                return line;
            } catch (Exception ex) {
                System.err.println("REQUEST SEND FAILS - " + ex.getMessage() + " (" + ex.getClass().getSimpleName() + ")");
            } finally {
                if (s != null) {
                    try {
                        s.close();
                    } catch (IOException ex) {
                        Logger.getLogger(PLCComUnit.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }

            if (i + 1 < REQUEST_SEND_MAXIMUM_RETRIES) {
                System.err.println("Retrying");
            }
        }

        return "";
    }

    private void maintain(PLC plc) throws IOException {
        PLCHighLevel highLevel = hlv.get(plc);
        if (highLevel.getLockStatus() == 0) {
            List<CounterData> data = highLevel.readCounterData(highLevel.getEEPPointer());

            saveCounterData(data);

            highLevel.setPLCDateTime();
            for (Valve i : DatabaseUnit.UNIT.selectValvesOnPLC(plc.getIp())) {
                highLevel.setValveStatus(i.getPortNum(), true);
            }
            for (Counter i : DatabaseUnit.UNIT.selectCountersOnPLC(plc.getIp())) {
                highLevel.setCounterStatus(i.getPort(), true);
            }
            highLevel.unlockPLC();
        } else {
            List<CounterData> data = highLevel.readCounterData(highLevel.getDMPointer());
           
            saveCounterData(data);
        }
        for (Valve i : DatabaseUnit.UNIT.selectValvesOnPLC(plc.getIp())) {
            highLevel.addSchedulesToPLC(i.getPortNum(), DatabaseUnit.UNIT.selectTenClosestSchedules(i.getId()));
        }
        // this should be updated to management console view (if we add the status on view screen).
        //highLevel.getCounterStatuses();
        //highLevel.getValveStatuses();
    }

    public static void main(String[] args) throws IOException, InterruptedException {
//        String ret = UNIT.sendRequest("/", "192.168.0.30");
        String ret = UNIT.sendRequest("/HOSTLINK/RVD0002", "192.168.1.30");

        PLCHighLevel hl = new PLCHighLevel("192.168.1.30");

//        for (CounterData x : hl.readCounterData(hl.getEEPPointer())){
//            System.out.println("" + x);
//        }
//        hl.setPLCDateTime();
//        String a = "";
//        for(boolean x : hl.getValveStatuses()){
//           a = a + " : " + x;
//        }
//        String b = "" + hl.getValveStatus(1);
//        String c = "" + hl.getCounterStatus(1);
        //hl.setCounterStatus(3, PLCHighLevel.Status.DISABLED);
//        hl.unlockPLC();
//        hl.setPLCDateTime();
//        
//        System.out.println("Ret a Is: " + a);
//        System.out.println("Ret b Is: " + b);
//        System.out.println("Ret c Is: " + c);
        hl.turnOffValve(1);
        hl.turnOffValve(2);
        hl.turnOffValve(3);
        hl.turnOffValve(4);
    }

    private void saveCounterData(List<CounterData> data) {
        for (CounterData d : data) {
            DatabaseUnit.UNIT.insertOrUpdate(d, false);
        }
    }

    private void handleEvent(Event e) throws IOException {
        System.out.println("Running Function: handle event");
        Object object = e.getField(DatabaseUnit.DBCE_DATA);
        DatabaseUnit.ChangeType changeType = (DatabaseUnit.ChangeType) e.getField(DatabaseUnit.DBCE_CHANGE_TYPE);
        if (object instanceof Counter){
            handleCounterChange((Counter)object, changeType);
        }else if (object instanceof Valve){
            handleValveChange((Valve)object, changeType);
        }else if (object instanceof PLC){
            handlePLCChange((PLC)object, changeType);
        }
    }

    private void handleCounterChange(Counter counter, ChangeType changeType) throws IOException {
        PLCHighLevel highLevel = hlv.get(DatabaseUnit.UNIT.selectItem(PLC.class, counter.getPlcIP()));
        System.out.println("handleCounterChange: ");
        switch (changeType){
            case DELETE:
                highLevel.setCounterStatus(counter.getPort(), false);
                System.out.println("Remove Counter: " + counter.getPort());
                break;
            case EDIT_OR_INSERT:
                highLevel.setCounterStatus(counter.getPort(), true);
                System.out.println("Add/Edit Counter: " + counter.getPort());
                break;
        }
    }

    private void handleValveChange(Valve valve, ChangeType changeType) throws IOException {
        PLCHighLevel highLevel = hlv.get(DatabaseUnit.UNIT.selectItem(PLC.class, valve.getPlcIp()));
        System.out.println("handleValveChange: ");
        switch (changeType){
            case DELETE:
                highLevel.setValveStatus(valve.getPortNum(), false);
                System.out.println("Remove Valve: " + valve.getPortNum());
                break;
            case EDIT_OR_INSERT:
                highLevel.setValveStatus(valve.getPortNum(), true);
                System.out.println("Add/Edit Valve: " + valve.getPortNum());
                break;
        }
    }

    private void handlePLCChange(PLC plc, ChangeType changeType) {
        switch (changeType){
            case DELETE:
                hlv.remove(plc);
                break;
            case EDIT_OR_INSERT:
                hlv.put(plc, new PLCHighLevel(plc.getIp()));
                break;
        }
    }
}
